/*
 * File:    SoapRegistry.java
 * Created: 05-Dec-2007
 * Version: $Id$
 *
 * COPYRIGHT (C) 2007, Bitgate Software, LLC.  All Rights Reserved.
 * software@bitgatesoftware.com
 */

package com.bitgate.util.soap;

import java.util.HashMap;
import java.util.List;

import com.bitgate.util.config.Config;
import com.bitgate.util.dynaload.Dynaload;

import static com.bitgate.util.debug.Debug.info;
import static com.bitgate.util.debug.Debug.debug;
import static com.bitgate.util.debug.Debug.isDebugEnabled;

/**
 * This class is a cache class that contains information about a SOAP object class.  The class can then be pulled from
 * cache, and instantiated inside the system as a SOAP request or response.
 * 
 * @author kenji
 * @since 0.7.0
 */
public class SoapRegistry
{
	private final HashMap<String, HashMap<String, SoapRegistryEntry>> registry;
	private final HashMap<String, HashMap<String, Class<?>>> topLevelRegistry;
	private static final SoapRegistry _default = new SoapRegistry();
	
	private SoapRegistry()
	{
		registry = new HashMap<String, HashMap<String, SoapRegistryEntry>>();
		topLevelRegistry = new HashMap<String, HashMap<String, Class<?>>>();
		
		String soapFilename = System.getProperty("soap.config");
		
		if (soapFilename == null) {
			soapFilename = "conf/defaults/soap.xml";
		}
		
		Config.getDefault().register(soapFilename, "soap");
		
		int numSoapObjects = Integer.parseInt(Config.getDefault().parseConfig("soap", "count(//soap/object)"));
		
		for(int i = 1; i < (numSoapObjects + 1); i++) {
			String rootNode = "//soap/object[" + i + "]";
			String soapType = Config.getDefault().parseConfig("soap", rootNode + "/@type");
			String soapName = Config.getDefault().parseConfig("soap", rootNode + "/@name");
			
			if (soapType.equals("jar")) {
				String soapJarfile = Config.getDefault().parseConfig("soap", rootNode + "/property[@type='soap.jarfile']/@value");
				
				if (soapJarfile != null && !soapJarfile.equals("")) {
					loadFromJarfile(soapName, soapJarfile);
				} else {
					info("Unable to parse soap entry name '" + soapName + "': no JAR file was specified.");
				}
			} else if (soapType.equals("class")) {
				String soapClassname = Config.getDefault().parseConfig("soap", rootNode + "/property[@type='soap.classname']/@value");
				
				if (soapClassname != null && !soapClassname.equals("")) {
					loadFromClassname(soapName, soapClassname);
				} else {
					info("Unable to parse soap entry name '" + soapName + "': no Class name was specified.");
				}
			} else {
				info("Unable to parse soap type '" + soapType + "' for soap config name '" + soapName + "'");
			}
		}
		
		if (isDebugEnabled()) {
			debug("Registry Entries = '" + registry.toString() + "'");
		}
	}
	
	private void loadFromJarfile(String name, String jarfile)
	{
		if (isDebugEnabled()) {
			debug("Loading SOAP registry from file '" + jarfile + "'");
		}
		
		Dynaload dynaload = null;
		
		try {
			dynaload = new Dynaload(jarfile);
		} catch(ClassNotFoundException e) {
			info("Unable to dynamically load JAR file '" + jarfile + "': " + e.getMessage());
			return;
		}
		
		String[] knownClasses = dynaload.getCachedClasses("com.*");
		HashMap<String, SoapRegistryEntry> registryEntries = new HashMap<String, SoapRegistryEntry>();
		
		for(String className : knownClasses) {
			Class<?> classObject = null;
			String shortClassname = className.substring(className.lastIndexOf(".") + 1);
			
			try {
				classObject = dynaload.loadClass(className);
			} catch(ClassNotFoundException e) {
				info("Unable to load class '" + className + "': " + e.getMessage());
				continue;
			}
			
			SoapQuery query = new SoapQuery();
			List<String> annotatedEntries = null;
			String entryPoint = null;
			boolean requireSession = false;
			
			try {
				entryPoint = query.getEntryPoint(classObject);
				
				if (entryPoint.endsWith("/")) {
					entryPoint = entryPoint.substring(0, entryPoint.length() - 1);
				}
				
				if (isDebugEnabled()) {
					debug("Annotation Entry Point: " + entryPoint);
				}
			} catch(SoapException e) {
				info("Unable to retrieve entry point: " + e.getMessage());
				continue;
			}
			
			try {
				requireSession = query.getRequireSession(classObject);
				
				if (isDebugEnabled()) {
					debug("Annotation Require Session: " + requireSession);
				}
			} catch(SoapException e) {
				info("Unable to retrieve require session flag: " + e.getMessage());
				continue;
			}
			
			try {
				annotatedEntries = query.getPublishedObjects(classObject);
			} catch(SoapException e) {
				info("Unable to parse for annotated publishable objects: " + e.getMessage());
				continue;
			}
			
			if (isDebugEnabled()) {
				debug("Class name '" + className + "'");
			}
			
			for(String functionName : annotatedEntries) {
				String requestEntryPoint = entryPoint + "/" + shortClassname + "/" + functionName;
				SoapRegistryEntry srEntry = new SoapRegistryEntry(requireSession, classObject);
				
				if (isDebugEnabled()) {
					debug("Annotated publishable object: '" + functionName + "' (Short='" + shortClassname + "') URL=" +
						requestEntryPoint);
				}
				
				registryEntries.put(requestEntryPoint, srEntry);
			}
			
			String topLevelRegistryName = entryPoint + "/" + shortClassname;
			HashMap<String, Class<?>> topLevel = topLevelRegistry.get(name);
			
			if (topLevel == null) {
				topLevel = new HashMap<String, Class<?>>();
				topLevelRegistry.put(name, topLevel);
			}
			
			topLevel.put(topLevelRegistryName, classObject);
		}
		
		registry.put(name, registryEntries);
	}
	
	private void loadFromClassname(String name, String classname)
	{
		if (isDebugEnabled()) {
			debug("Loading SOAP registry from classname '" + classname + "'");
		}
		
		HashMap<String, SoapRegistryEntry> registryEntries = new HashMap<String, SoapRegistryEntry>();
		Class<?> classObject = null;
		
		try {
			classObject = Class.forName(classname);
		} catch(Exception e) {
			info("Unable to instantiate class name '" + classname + "' for SOAP use: " + e.getClass().getName());
			return;
		}
		
		String shortClassname = classname.substring(classname.lastIndexOf(".") + 1);
		SoapQuery query = new SoapQuery();
		List<String> annotatedEntries = null;
		String entryPoint = null;
		boolean requireSession = false;
		
		try {
			entryPoint = query.getEntryPoint(classObject);
			
			if (entryPoint.endsWith("/")) {
				entryPoint = entryPoint.substring(0, entryPoint.length() - 1);
			}
			
			if (isDebugEnabled()) {
				debug("Annotation Entry Point: " + entryPoint);
			}
		} catch(SoapException e) {
			info("Unable to retrieve entry point: " + e.getMessage());
			return;
		}
		
		try {
			requireSession = query.getRequireSession(classObject);
			
			if (isDebugEnabled()) {
				debug("Annotation Require Session: " + requireSession);
			}
		} catch(SoapException e) {
			info("Unable to retrieve require session flag: " + e.getMessage());
			return;
		}
		
		try {
			annotatedEntries = query.getPublishedObjects(classObject);
		} catch(SoapException e) {
			info("Unable to parse for annotated publishable objects: " + e.getMessage());
			return;
		}
		
		if (isDebugEnabled()) {
			debug("Class name '" + classname + "'");
		}
		
		for(String functionName : annotatedEntries) {
			String requestEntryPoint = entryPoint + "/" + shortClassname + "/" + functionName;
			SoapRegistryEntry srEntry = new SoapRegistryEntry(requireSession, classObject);
			
			if (isDebugEnabled()) {
				debug("Annotated publishable object: '" + functionName + "' (Short='" + shortClassname + "') URL=" +
					requestEntryPoint);
			}
			
			registryEntries.put(requestEntryPoint, srEntry);
		}
		
		String topLevelRegistryName = entryPoint + "/" + shortClassname;
		HashMap<String, Class<?>> topLevel = topLevelRegistry.get(name);
		
		if (topLevel == null) {
			topLevel = new HashMap<String, Class<?>>();
			topLevelRegistry.put(name, topLevel);
		}
		
		topLevel.put(topLevelRegistryName, classObject);
		registry.put(name, registryEntries);
	}

	/**
	 * Returns the default instance.
	 * 
	 * @return <code>SoapRegistry</code> object.
	 */
	public static final SoapRegistry getDefault()
	{
		return _default;
	}
	
	/**
	 * Checks whether or not the given URL (by registry name) matches an entry in the registry.
	 * 
	 * @param registryName <code>String</code> containing the name of the registry.
	 * @param url <code>String</code> containing the URL to search.
	 * @return <code>true</code> if the URL given is a SOAP URL, <code>false</code> otherwise.
	 */
	public boolean isSoapUrl(String registryName, String url)
	{
		HashMap<String, SoapRegistryEntry> registryEntries = registry.get(registryName);
		
		if (registryEntries == null) {
			return false;
		}
		
		return (registryEntries.get(url) != null);
	}
	
	/**
	 * Checks whether or not the given URL (by registry name) matches an entry in the top-level registry.
	 * 
	 * @param registryName <code>String</code> containing the name of the registry.
	 * @param url <code>String</code> containing the URL to search.
	 * @return <code>true</code> if the URL given is a SOAP Top-Level URL, <code>false</code> otherwise.
	 */
	public boolean isTopLevelUrl(String registryName, String url)
	{
		return (getTopLevelUrlClass(registryName, url) != null);
	}
	
	/**
	 * Returns the class for the top level registry URL object.
	 * 
	 * @param registryName <code>String</code> containing the name of the registry.
	 * @param url <code>String</code> containing the URL to search.
	 * @return <code>Class&lt;?&gt;</code> containing the object for the SOAP registry.
	 */
	public Class<?> getTopLevelUrlClass(String registryName, String url)
	{
		HashMap<String, Class<?>> topLevel = topLevelRegistry.get(registryName);
		
		if (topLevel == null) {
			return null;
		}
		
		return topLevel.get(url);
	}
}